#ifndef CHANNELLIST_H_
#define CHANNELLIST_H_

#include <gtkmm.h>
#include <map>

#include "conf/collection.h"
#include "conf/attribute.h"
#include "conf.h"
#include "menu.h"
#include "widgets.h"

#include "gtkversion.h"

namespace gpyp {

class Application;
class YPCollection;
typedef boost::shared_ptr<YPCollection> RefYPCollection;
class ChannelList;

class ChannelListColumn : public conf::Element {
  friend class conf::Collection<ChannelListColumn, ChannelList>;
private:
  typedef Gtk::TreeViewColumn::SlotCellData SlotCellData;

  static const int              MIN_WIDTH;

  Application                  &app_;
  ChannelList                  &channellist_;

  conf::RefAttributeBool        enable_;
  conf::RefAttributeStr         title_;
  RefAttributeFormat            format_;
  conf::RefAttributeInt         width_;
  conf::RefAttributeBool        right_;
  conf::RefAttributeBool        sortasc_;
  conf::RefAttributeBool        expand_;
  conf::RefAttributeBool        ellipsize_;

  Gtk::CellRendererText         renderer_;
  Gtk::TreeViewColumn           treeviewcolumn_;

protected:
  virtual void on_presave();

protected:
  void update_ellipsize();

  void width_on_loaded();
  void right_on_changed();
  void expand_on_changed();
  void ellipsize_on_changed();

  void column_on_clicked();

public:
  inline conf::RefAttributeBool enable() {
    return enable_;
  }
  inline conf::RefAttributeStr title() {
    return title_;
  }
  inline RefAttributeFormat format() {
    return format_;
  }
  inline conf::RefAttributeInt width() {
    return width_;
  }
  inline conf::RefAttributeBool right() {
    return right_;
  }
  inline conf::RefAttributeBool sortasc() {
    return sortasc_;
  }
  inline conf::RefAttributeBool expand() {
    return expand_;
  }
  inline conf::RefAttributeBool ellipsize() {
    return ellipsize_;
  }

  Glib::ustring format(RefChannel channel);

  inline Gtk::CellRendererText &get_renderer() {
    return renderer_;
  }
  inline Gtk::TreeViewColumn &get_treeviewcolumn() {
    return treeviewcolumn_;
  }

  int compare(Gtk::TreeIter iter1, Gtk::TreeIter iter2, bool ascending);

  ChannelListColumn(const Glib::ustring &name,
                    Application         &app,
                    ChannelList         &channellist,
                    SlotCellData         slot_celldata);
};
typedef boost::shared_ptr<ChannelListColumn> RefChannelListColumn;

class ModelManager : public conf::Element {
private:
  sigc::signal<void> signal_sort_column_changed_;
  sigc::signal<void> signal_model_created_;
public:
  inline sigc::signal<void> signal_sort_column_changed() {
    return signal_sort_column_changed_;
  }
  inline sigc::signal<void> signal_model_created() {
    return signal_model_created_;
  }

  typedef Gtk::TreeSortable::SlotCompare SlotCompare;
  typedef sigc::slot<bool, RefChannel>   SlotFilter;

private:
  typedef Gtk::TreeModelColumn<RefChannel>    ChColumn;
  typedef Gtk::TreeModelColumn<Glib::ustring> StrColumn;
  typedef boost::shared_ptr<StrColumn>        RefStrColumn;

  class ColumnData;
  typedef boost::shared_ptr<ColumnData> RefColumnData;
  class ColumnData : boost::noncopyable {
  private:
    ModelManager        &manager_;

    RefChannelListColumn cl_column_;
    RefStrColumn         model_column_;

  public:
    inline static RefColumnData create(ModelManager        &manager,
                                       RefChannelListColumn cl_column) {
      return RefColumnData(new ColumnData(manager,
                                          cl_column,
                                          RefStrColumn(new StrColumn())));
    }

    inline int compare(Gtk::TreeIter iter1, Gtk::TreeIter iter2) {
      return cl_column_->compare(iter1, iter2, *manager_.sort_asc_);
    }
    inline sigc::slot<int, Gtk::TreeIter, Gtk::TreeIter> compare_slot() {
      return sigc::mem_fun(*this, &ColumnData::compare);
    }

    inline StrColumn &operator*() {
      return *model_column_;
    }

  protected:
    inline ColumnData(ModelManager        &manager,
                      RefChannelListColumn cl_column,
                      RefStrColumn         model_column) :
      manager_     (manager),
      cl_column_   (cl_column),
      model_column_(model_column)
    {
    }
  };

private:
  Application &app_;
  ChannelList &cl_;

  conf::RefAttributeInt  sort_column_;
  conf::RefAttributeBool sort_asc_;

  boost::shared_ptr<ChColumn>                   column_ch_;
  boost::shared_ptr<Gtk::TreeModelColumnRecord> columns_;
  Glib::RefPtr<Gtk::ListStore>                  liststore_;
  Glib::RefPtr<Gtk::TreeModelFilter>            filter_;

  SlotCompare slot_compare_;
  SlotFilter  slot_filter_;

  typedef std::map<RefChannelListColumn, RefColumnData> ColumnMap;
  ColumnMap column_map_;

private:
  void conf_on_loaded();
  void recreate_columns();
  void recreate_model();
  void recreate_model_full();

  void cl_on_column_added(RefChannelListColumn column);

  int default_sort_func(Gtk::TreeIter iter1, Gtk::TreeIter iter2);

  void on_sort_column_changed();

  bool visible_func(const Gtk::TreeIter &iter);

public:
  Gtk::TreeIter add(RefChannel ch, bool check = false);
  void recreate();

  void set_filter_func(const SlotFilter &slot_filter);
  void refilter();

  void resort();

  RefChannel get_channel(const Gtk::TreeIter &iter);
  Glib::ustring get_value(const Gtk::TreeIter &iter, RefChannelListColumn column);
  StrColumn *get_modelcolumn(ChannelListColumn &column);

  inline Gtk::TreeIter convert(const Gtk::TreeIter &iter) {
    return filter_->convert_child_iter_to_iter(iter);
  }
  inline Gtk::TreeIter convert(const Gtk::TreePath &path) {
    return convert(liststore_->get_iter(path));
  }

  inline Glib::RefPtr<Gtk::TreeModelFilter> get() {
    return filter_;
  }
  inline Glib::RefPtr<Gtk::ListStore> get_liststore() {
    return liststore_;
  }

  bool get_sort_column_id(int &sort_column_id, Gtk::SortType &order) const;

  void set_attributes();

  ModelManager(const Glib::ustring &name,
               Application         &app,
               ChannelList         &cl,
               SlotCompare          slot_compare = SlotCompare());
};
typedef boost::shared_ptr<ModelManager> RefModelManager;

class ChannelList : public conf::Collection<ChannelListColumn, ChannelList>,
                    public WidgetInput<TreeView> {
  friend class ChannelListColumn;
public:
  enum ColumnOpt {
    COLUMNOPT_NONE      = 0,
    COLUMNOPT_RIGHT     = 1 << 0,
    COLUMNOPT_SORTDESC  = 1 << 1,
    COLUMNOPT_EXPAND    = 1 << 2,
    COLUMNOPT_ELLIPSIZE = 1 << 3,
  };

private:
  Application              &app_;

  conf::RefAttributeDouble  sort_color_;
  RefAttributeCLModel       model_;

  RefModelManager           model_default_;
  RefModelManager           model_played_;
  RefModelManager           model_ended_;

  RefModelManager           model_selected_;
  sigc::connection          conn_sort_column_changed_;
  sigc::connection          conn_model_created_;

  sigc::connection          conn_item_reordered_;
  sigc::connection          conn_columns_changed_;

  Gtk::Widget              *search_parent_;
  Gtk::Entry               *search_entry_;

  RefChannelListColumn      sort_column_;

  ChannelListMenu           menu_;

  BuiltinCommand           &bc_cl_;

private:
  void treeviewcolumn_set_arrow(Gtk::TreeViewColumn &column, Gtk::SortType sort_type);

protected:
  virtual RefChannelListColumn on_create_item(const Glib::ustring &name);
  virtual void on_load_default();

  virtual void prepare_commandopts(CommandOpts &opts);

#ifndef GTK_CELL_LAYOUT_HAVE_GET_CELLS
  virtual GtkCellRenderer *get_tooltip_renderer(GtkTreeViewColumn *column);
#endif

  void select_model(RefModelManager model);
  void model_on_created();

  void model_on_changed();

  void on_cell_data(Gtk::CellRenderer *cell, const Gtk::TreeIter &iter);

  void ypcollection_on_loaded();

  virtual bool on_popup(guint button, guint32 activate_time);
  virtual bool on_key_press_event(GdkEventKey *event);
  virtual void on_realize();

  virtual void on_add_item(RefChannelListColumn item);
  virtual void on_remove_item(iterator iter, RefChannelListColumn item);
  void _on_item_reordered(RefItem column, RefItem prev, RefItem next);
  void _on_columns_changed();

  void model_on_sort_column_changed();

  void on_play(RefChannel ch);
  int played_sort_func(Gtk::TreeIter iter1, Gtk::TreeIter iter2);
  int ended_sort_func(Gtk::TreeIter iter1, Gtk::TreeIter iter2);

  template<typename T>
  void connect_queue_draw(T attr) {
    attr->signal_changed()
      .connect(sigc::mem_fun(*this, &ChannelList::queue_draw));
  }

public:
  inline conf::RefAttributeDouble sort_color() {
    return sort_color_;
  }
  inline RefAttributeCLModel model() {
    return model_;
  }

  RefChannelListColumn add_item();
  RefChannelListColumn add_item(const Glib::ustring &title,
                                const Glib::ustring &format,
                                ColumnOpt opt = COLUMNOPT_NONE);

  inline void set_model(const Glib::RefPtr<Gtk::TreeModel> &model) {
    Gtk::TreeView::set_model(model);
    set_search_column(-1);
  }

  RefChannel get_selected_channel();
  void select_iter(Gtk::TreeIter iter);
  void select_first();
  void select_last();
  void select_next();
  void select_prev();

  void set_filter_func(ModelManager::SlotFilter slot_filter);

  void refilter();
  void resort();

  void set_search_widget(Gtk::Widget &parent, Gtk::Entry &entry);

  void clear_played();
  void clear_ended();

private:
  bool bc_show_default (ShellLine &line, CommandOpts &opts);
  bool bc_show_played  (ShellLine &line, CommandOpts &opts);
  bool bc_show_ended   (ShellLine &line, CommandOpts &opts);
  bool bc_toggle_played(ShellLine &line, CommandOpts &opts);
  bool bc_toggle_ended (ShellLine &line, CommandOpts &opts);
  bool bc_clear_played (ShellLine &line, CommandOpts &opts);
  bool bc_clear_ended  (ShellLine &line, CommandOpts &opts);
  bool bc_clear        (ShellLine &line, CommandOpts &opts);
  bool bc_resort       (ShellLine &line, CommandOpts &opts);
  bool bc_refilter     (ShellLine &line, CommandOpts &opts);

public:
  ChannelList(Application &app, MainWindow &mainwindow);
};
typedef boost::shared_ptr<ChannelList> RefChannelList;

inline ChannelList::ColumnOpt operator|(ChannelList::ColumnOpt opt1, ChannelList::ColumnOpt opt2) {
  return static_cast<ChannelList::ColumnOpt>(static_cast<unsigned>(opt1) | static_cast<unsigned>(opt2));
}
inline ChannelList::ColumnOpt operator&(ChannelList::ColumnOpt opt1, ChannelList::ColumnOpt opt2) {
  return static_cast<ChannelList::ColumnOpt>(static_cast<unsigned>(opt1) & static_cast<unsigned>(opt2));
}
inline ChannelList::ColumnOpt operator^(ChannelList::ColumnOpt opt1, ChannelList::ColumnOpt opt2) {
  return static_cast<ChannelList::ColumnOpt>(static_cast<unsigned>(opt1) ^ static_cast<unsigned>(opt2));
}
inline ChannelList::ColumnOpt operator~(ChannelList::ColumnOpt opt) {
  return static_cast<ChannelList::ColumnOpt>(~static_cast<unsigned>(opt));
}
inline ChannelList::ColumnOpt &operator|=(ChannelList::ColumnOpt &opt1, ChannelList::ColumnOpt opt2) {
  return (opt1 = static_cast<ChannelList::ColumnOpt>(static_cast<unsigned>(opt1) | static_cast<unsigned>(opt2)));
}
inline ChannelList::ColumnOpt &operator&=(ChannelList::ColumnOpt &opt1, ChannelList::ColumnOpt opt2) {
  return (opt1 = static_cast<ChannelList::ColumnOpt>(static_cast<unsigned>(opt1) & static_cast<unsigned>(opt2)));
}
inline ChannelList::ColumnOpt &operator^=(ChannelList::ColumnOpt &opt1, ChannelList::ColumnOpt opt2) {
  return (opt1 = static_cast<ChannelList::ColumnOpt>(static_cast<unsigned>(opt1) ^ static_cast<unsigned>(opt2)));
}

}/*gpyp*/

#endif/*CHANNELLIST_H_*/
